package com.ruoyi.pay.refund.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.config.mapper.ApplicationConfigMapper;
import com.ruoyi.config.mapper.WxpayConfigMapper;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 🤙🤙🏼🤙🏽
 * @date 2025 4月 16 12:00
 * @Description
 */
@Component
@Slf4j
public class HttpUtil {
    @Resource
    private WxpayConfigMapper wxpayConfigMapper;
    @Resource
    private ApplicationConfigMapper applicationConfigMapper;

    /**
     * 保存微信平台证书
     */
    private final ConcurrentHashMap<String, AutoUpdateCertificatesVerifier> verifierMap = new ConcurrentHashMap<>();


    /**
     * 功能描述:获取平台证书，自动更新
     * 注意：这个方法内置了平台证书的获取和返回值解密
     *
     * @return com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier
     * @author zhouwenjie
     * @date 2021/6/13 20:29
     */
    public com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier getVerifier(String serialNumber) {
        com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier verifier = null;
        if (verifierMap.isEmpty() || !verifierMap.containsKey(serialNumber)) {
            verifierMap.clear();
            try {
                //刷新
                String mchId = wxpayConfigMapper.selectOneData("mchId");
                String privateKeyPath = wxpayConfigMapper.selectOneData("privateKeyPath");
                String mchSerialNo = wxpayConfigMapper.selectOneData("mchSerialNo");
                String apiV3Key = wxpayConfigMapper.selectOneData("apiV3Key");
                PrivateKeySigner signer = new PrivateKeySigner(mchSerialNo, getPrivateKey(privateKeyPath));
                WechatPay2Credentials credentials = new WechatPay2Credentials(mchId, signer);
                verifier = new com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier(credentials
                        , apiV3Key.getBytes("utf-8"));
                verifierMap.put(verifier.getValidCertificate().getSerialNumber() + "", verifier);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        } else {
            verifier = verifierMap.get(serialNumber);
        }
        return verifier;
    }

    public PrivateKey getPrivateKey(String url) {
        log.info("weChatPayPrivateKeyUrl:{}", url);

        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet request = new HttpGet(url);
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                String content = EntityUtils.toString(response.getEntity(), "utf-8");
                String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                        .replace("-----END PRIVATE KEY-----", "")
                        .replaceAll("\\s+", "");
                KeyFactory kf = KeyFactory.getInstance("RSA");
                PrivateKey key = kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
                return key;
            }
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        } catch (IOException e) {
            e.printStackTrace();
        }
        log.info("获取私钥已完成");
        return null;
    }

    /**
     * 获取httpClient,用于之后请求微信接口
     * 用这个工具类的好处就是不用你自己去拼接签名参数了，每次请求会自动帮你创建签名
     * 详情可以点击进去看源码
     */
    public CloseableHttpClient getHttpClient(String serialNumber) {
        //获取平台证书
        AutoUpdateCertificatesVerifier verifier = getVerifier(serialNumber);
        if (verifier == null) {
            throw new RuntimeException("证书获取异常");
        }
        String privateKeyPath = wxpayConfigMapper.selectOneData("privateKeyPath");
        String mchSerialNo = wxpayConfigMapper.selectOneData("mchSerialNo");
        String mchId = wxpayConfigMapper.selectOneData("mchId");
        // 初始化httpClient  httpClient会在发送请求之前拦截并且自动根据请求信息创建签名
        CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, getPrivateKey(privateKeyPath))
                .withValidator(new WechatPay2Validator(verifier)).build();
        return httpClient;
    }

    /**
     * 功能描述: 发起get请求
     * 动态返回自定义的实体类型
     *
     * @param method 请求方式 GET POST
     * @param url
     * @param t      泛型
     * @return T 实体类class
     * @author zhouwenjie
     * @date 2021/6/15 14:49
     */
    public <T> T wxHttpRequest(String method, String url, String requestData, Class<T> t) {
        T resultBean = null;
        T instance = null;
        CloseableHttpResponse response;
        CloseableHttpClient httpClient = null;
        try {
            if (!verifierMap.isEmpty()) {
                String bigInteger = verifierMap.keys().nextElement();
                //httpClient 内置有签名信息
                httpClient = getHttpClient(bigInteger);
            } else {
                httpClient = getHttpClient("1");
            }
            //创建实体，相当于new对象
            instance = t.newInstance();
            if ("get".equalsIgnoreCase(method)) {
                HttpGet httpGet = new HttpGet(url);
                httpGet.setHeader("Accept", "application/json");
                //完成签名并执行请求
                response = httpClient.execute(httpGet);
            } else {
                StringEntity entity = new StringEntity(requestData, StandardCharsets.UTF_8);
                entity.setContentType("application/json");
                HttpPost httpPost = new HttpPost(url);
                httpPost.setEntity(entity);
                httpPost.setHeader("Accept", "application/json");
                //完成签名并执行请求
                response = httpClient.execute(httpPost);
            }
            //如果状态码为200,就是正常返回
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                String result = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
                JSONObject jsonObject = JSON.parseObject(result);
                if (instance instanceof JSONObject) {
                    resultBean = (T) jsonObject;
                } else {
                    resultBean = JSONObject.parseObject(jsonObject.toString(), t);
                }
            } else if (statusCode == 204) {
                log.info("关单成功");
                String code = "204";
                resultBean = (T) code;
            } else {
                log.error("错误状态码 = " + statusCode + ",返回的错误信息 = " + EntityUtils.toString(response.getEntity()));
                throw new RuntimeException(EntityUtils.toString(response.getEntity()));
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return resultBean;
    }
}
